/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { DslRule } from '~/dsl/Defines';
import { AwaitMatch } from '~/dsl/Descriptor';
import { DefineRuleContext, RuleContext } from '~/dsl/Context';
import { EngineCapabilityHint } from '~/extensions/hint/CapabilityHint';

/**
 * The entry for the DSL rule.
 *
 * It is the underlying implementation of {@link rule} function in the DSL.
 *
 * @category Adapter
 */
export type DslEntry<P, H extends EngineCapabilityHint, U extends DefineRuleContext<P, H>> = (
    init: (context: RuleContext<H>) => P
) => AwaitMatch<H, U>;

/**
 * The API provided by a DSL adapter.
 *
 * See {@link DslAdapter} for the default implementation, where custom adapter
 * only needs to implement `entry` and `loadDslRule`.
 *
 * @typeParam T The concrete checker/rule type.
 *
 * @category Adapter
 */
export interface DslAdapterCapability<T> {
    /**
     * The entry point for the DSL rule.
     *
     * This is used internally to create DSL rules, so you only need to provide
     * the implementation of this function.
     */
    entry: DslEntry<any, any, any>;

    /**
     * Convert the DSL rule to the native rule.
     *
     * The built-in DSL rules can be directly imported, so we can import and load them.
     *
     * @param dslRule The DSL rule to be loaded.
     *
     * @example
     *
     * ```typescript
     * import XxxRule from 'xxx';
     *
     * const rule = adapter.loadDslRule(XxxRule);
     * ```
     */
    loadDslRule(dslRule: DslRule<T>): T;

    /**
     * Load DSL from external file and convert it to the native rule.
     *
     * For user-written DSL rules, or those under development, we can load them from file.
     * A drawback is that it is an async operation, and async spreads like a virus.
     *
     * FIXME: Can it work for DSL file with external dependencies? I think not?
     *
     * @param path Path to the DSL file.
     *
     * @example
     *
     * ```typescript
     * const rule = await adapter.loadDslRuleFromFile('./path/to/rule.ts');
     * ```
     */
    loadDslRuleFromFile(path: string): Promise<T>;
}

/**
 * The default implementation of the DSL adapter.
 *
 * It will automatically register itself on created. Extends it to create custom adapters.
 * For the concrete adapter, you should specify `T` to the concrete type of the rule.
 *
 * You must call `super()` in the derived adapter's constructor to register it.
 *
 * Import it before you import any DSL rules.
 *
 * @example
 *
 * ```typescript
 * ...
 * import { YourAdapter } from 'your-adapter';
 * ...
 * import { AnyDslRule } from 'your-dsl';
 * ...
 * ```
 *
 * @typeParam T The concrete checker/rule type.
 *
 * @category Adapter
 */
export abstract class DslAdapter<T> implements DslAdapterCapability<T> {
    protected constructor() {
        registerDslAdapter(this);
    }

    abstract entry: DslEntry<any, any, any>;

    abstract loadDslRule(dslRule: DslRule<T>): T;

    async loadDslRuleFromFile(path: string): Promise<T> {
        const module = await import(path);
        const dslRule = module.default;
        return this.loadDslRule(dslRule);
    }
}

let activeAdapter: DslAdapterCapability<any> | undefined = undefined;

/**
 * Register a DSL adapter for the native engine.
 *
 * The concrete adapter should call this function to register itself.
 *
 * @param adapter The adapter to register.
 *
 * @internal
 *
 * @category Adapter
 */
export function registerDslAdapter(adapter: DslAdapterCapability<any>): void {
    activeAdapter = adapter;
}

/**
 * Get the registered DSL adapter.
 *
 * @internal
 *
 * @category Adapter
 */
export function getDslAdapter(): DslAdapterCapability<any> {
    if (!activeAdapter) {
        throw new Error(
            'No DSL adapter registered.\nDid you forget to call `super` or did not create an adapter instance?'
        );
    }
    return activeAdapter;
}
